# -*- coding: utf8 -*-
"""
    centrifuge  的python2 客户端

        实现参考了 centrifuge-python (只有python3)
"""


import json
import time
import uuid
import websocket
import thread

import logging
logger = logging.getLogger('centrifuge')
logger.setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter('%(filename)s %(lineno)d %(message)s'))
logger.addHandler(stream_handler)

websocket.enableTrace(True)

class Credentials:
    """
    认证
    """
    def __init__(self, user, timestamp, info, token):
        logger.info('info: {}'.format(info))
        self.user = str(user)
        self.timestamp = str(timestamp)
        self.info = str(info)
        self.token = str(token)


class Subscription:
    """
    频道订阅
    """
    def __init__(self, client, channel, **kwargs):
        self.subscribed = False
        self.client = client
        self.channel = channel
        self.last_message_id = None
        self.handlers = {
            'subscribe_handle': kwargs.get('subscribe_handle'),         # 频道订阅成功时触发
            'message_handle': kwargs.get('message_handle'),             # 收到"message"时触发
            'join_handle': kwargs.get('join_handle'),                   # 有其它新客户端订阅时触发
            'leave_handle': kwargs.get('leave_handle'),                 # 有其它客户端取消订阅时触发
            # 'unsubscribe_handle': kwargs.get('unsubscribe_handle'),     # 取消订阅时触发
            # 'error_handle': kwargs.get('error_handle')                  # 发生错误时触发
        }

    def __str__(self):
        return '<instance Subscription> channel: {}, last_message_id: {}'.format(self.channel, self.last_message_id)

_RECONNECT_INTERVAL = 5
class CentrifugeClient:
    """
    Centriguge客户端
    """
    def __init__(self, url, credentials, **kwargs):
        self.credentials = credentials
        self.client_id = None
        self.url = url
        self._subs = {}
        self._ping_interval = kwargs.get('ping_interval', 25)
        self._pong_wait_timeout = kwargs.get('pong_wait_timeout', 5)
        self._init_websocket()
        self._reconnect_interval = _RECONNECT_INTERVAL

    def _init_websocket(self):
        self.ws = websocket.WebSocketApp(self.url)
        self.ws.on_open = self._handle_open
        self.ws.on_message = self._handle_message
        self.ws.on_close = self._handle_close
        self.ws.on_error = self._handle_error

    def _get_message(self, method, params, uid=None):
        message = {
            'uid': uid or uuid.uuid4().hex,
            'method': method,
            'params': params
        }
        return message

    def _reconnect(self):
        self.ws.close()
        time.sleep(self._reconnect_interval)
        self._init_websocket()
        self.connect()

    def _handle_open(self, ws):
        logger.info('websocket 已连接.')
        # 认证
        data = {
            'user': self.credentials.user,
            'timestamp': self.credentials.timestamp,
            'info': self.credentials.info,
            'token': self.credentials.token
        }
        self._send('connect', data)

    def _process_connect(self, data):
        body = data.get('body')
        self.client_id = body.get('client')
        if body.get('error'):
            logger.error('认证错误!')
            raise Exception(body['error'])
        logger.info('认证成功')

        # 订阅频道
        for sub in self._subs.values():
            data = {
                'channel': sub.channel,
            }
            # 重新订阅已订阅的频道
            if sub.subscribed:
                data.update({'recover': True, 'last': sub.last_message_id})
            self._send('subscribe', data)

    def _process_subscribe(self, data):
        """
        频道订阅
        """
        body = data.get('body', {})
        channel = body.get('channel')

        sub = self._subs.get(channel)
        if not sub:
            return

        if body.get('error'):
            logger.error('{} 订阅失败!'.format(channel))
            raise Exception(body['error'])
        else:
            sub.subscribed = True
            handle = sub.handlers.get('subscribe_handle')
            if handle:
                handle(**{'channel': sub.channel})
        logger.info('{} 订阅成功'.format(channel))

    def _process_message(self, data):
        """
        收到"message"
        """
        body = data.get('body')
        sub = self._subs.get(body.get('channel'))
        if not sub:
            return
        handle = sub.handlers.get('message_handle')
        if handle:
            sub.last_message_id = body.get('uid')
            handle(**body)

    def _process_join(self, data):
        """
        有其它新客户端订阅
        """
        body = data.get('body')
        sub = self._subs.get(body.get('channel'))
        if not sub:
            return
        handler = sub.handlers.get('join_handle')
        if handler:
            handler(**body)

    def _process_leave(self, data):
        """
        有其它客户端取消订阅
        """
        body = data.get('body')
        sub = self._subs.get(body.get('channel'))
        if not sub:
            return
        handler = sub.handlers.get('leave_handle')
        if handler:
            handler(**body)

    def _process_disconnect(self, response):
        """
        链接被关闭
        """
        body = response.get('body', {})
        reconnect = body.get('reconnect')
        reason = body.get('reason', '')
        logger.error('链接被关闭: {}'.format(reason))
        raise Exception(body)

    def _handle_message(self, ws, message):
        """
        有其它客户端取消订阅时触发
        """
        logger.debug('[ws recv]-> {}'.format(message))
        try:
            data = json.loads(message)
        except ValueError as e:
            logger.warning('不可json化,忽略.')
            return

        body = data.get('body')
        method = data.get('method', None)
        if method:
            cb = None
            if method == 'connect':
                cb = self._process_connect
            elif method == 'subscribe':
                cb = self._process_subscribe
            elif method == 'message':
                cb = self._process_message
            elif method == 'join':
                cb = self._process_join
            elif method == 'leave':
                cb = self._process_leave
            # elif method == 'publish':
            #     cb = self._process_publish_process_publish
            # elif method == 'presence':
            #     cb = self._process_presence
            # elif method == 'history':
            #     cb = self._process_history
            # elif method == 'ping':
            #     cb = self._process_ping
            elif method == 'disconnect':
                cb = self._process_disconnect
            if cb:
                try:
                    cb(data)
                except Exception as e:
                    logger.error(e)
                    self._reconnect()
            else:
                logger.debug(
                    'centrifuge: received message with unknown method %s', method)

    # def _handle_data(self, ws, data):
    #     logger.info('data -> {}'.format(data))

    def _handle_close(self, ws):
        """
        关闭时触发
        """
        logger.info('websocket 已关闭, 重新连接!')
        self._reconnect()

    def _handle_error(self, ws, e):
        """
        发生错误时触发
        """
        logger.error(e)
        self._reconnect()

    def subscribe(self, channel, **kwargs):
        sub = Subscription(self, channel, **kwargs)
        self._subs[channel] = sub
        return sub

    def _send(self, method, data, uid=None):
        message = self._get_message(method, data, uid)
        try:
            self.ws.send(json.dumps(message))
        except Exception as e:
            logger.error('发送失败: {}'.format(e))
            raise

    def send(self, method, data, uid=None):
        try:
            self._send(method, data, uid)
        except websocket.WebSocketConnectionClosedException as e:
            self._reconnect()
            raise
        except Exception as e:
            raise

    def connect(self):
        self.ws.run_forever(
            ping_interval=self._ping_interval,
            ping_timeout=self._pong_wait_timeout)


if __name__ == '__main__':
    from cent import generate_token
    WEBSOCKET_CLIENT_URL = 'ws://wktest.bjywkd.com:9111/connection/websocket'
    WEBSOCKET_SECRET = 'ywkd3742'

    user = 'newer'
    timestamp = str(int(time.time()))
    info = json.dumps({
        'first_name': 'Python',
        'last_name': 'Client'
    })
    print(WEBSOCKET_SECRET, user, timestamp, info)
    token = generate_token(WEBSOCKET_SECRET, user, timestamp, info=info)
    credentials = Credentials(**{
        'user': user,
        'timestamp': timestamp,
        'info': info,
        'token': token,
    })

    def subscribe_handle(channel):
        print('subscribe {} ok'.format(channel))

    def message_handle(uid, channel, data, client=None, info=None):
        print('{} recive message: {}'.format(channel, data))


    centrifuge_client = CentrifugeClient(WEBSOCKET_CLIENT_URL, credentials)
    centrifuge_client.subscribe('download_media:newer', **{
        'subscribe_handle': subscribe_handle,
        'message_handle': message_handle,
    })
    centrifuge_client.run_forever()
